package com.wngz.yudada.controller;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.wngz.yudada.annotation.AuthCheck;
import com.wngz.yudada.common.BaseResponse;
import com.wngz.yudada.common.DeleteRequest;
import com.wngz.yudada.common.ErrorCode;
import com.wngz.yudada.common.ResultUtils;
import com.wngz.yudada.constant.UserConstant;
import com.wngz.yudada.exception.BusinessException;
import com.wngz.yudada.exception.ThrowUtils;
import com.wngz.yudada.manager.AiManager;
import com.wngz.yudada.model.dto.question.*;
import com.wngz.yudada.model.entity.App;
import com.wngz.yudada.model.entity.Question;
import com.wngz.yudada.model.entity.User;
import com.wngz.yudada.model.enums.AppTypeEnum;
import com.wngz.yudada.model.vo.QuestionVO;
import com.wngz.yudada.service.AppService;
import com.wngz.yudada.service.ContextMemoryService;
import com.wngz.yudada.service.QuestionService;
import com.wngz.yudada.service.UserService;
import com.zhipu.oapi.service.v4.model.ModelData;
import io.reactivex.Flowable;
import io.reactivex.schedulers.Schedulers;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;

import static com.wngz.yudada.constant.AiQuestionConstant.GENERATE_QUESTION_SYSTEM_MESSAGE;

/**
 * 题目接口
 */
@RestController
@RequestMapping("/question")
@Slf4j
public class QuestionController {

    @Resource
    private QuestionService questionService;

    @Resource
    private UserService userService;

    @Resource
    private AiManager aiManager;

    @Resource
    private AppService appService;


    /**
     * 创建题目
     *
     * @param questionAddRequest
     * @param request
     * @return
     */
    @PostMapping("/add")
    @ApiOperation("创建题目")
    public BaseResponse<Long> addQuestion(@RequestBody QuestionAddRequest questionAddRequest, HttpServletRequest request) {
        ThrowUtils.throwIf(questionAddRequest == null, ErrorCode.PARAMS_ERROR);
        //  在此处将实体类和 DTO 进行转换
        Question question = new Question();
        BeanUtils.copyProperties(questionAddRequest, question);
        List<QuestionContentDto> questionContent = questionAddRequest.getQuestionContent();
        question.setQuestionContent(JSONUtil.toJsonStr(questionContent));
        // 数据校验
        questionService.validQuestion(question, true);
        //  填充默认值
        User loginUser = userService.getLoginUser(request);
        question.setUserId(loginUser.getId());
        // 写入数据库
        boolean result = questionService.save(question);
        ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR);
        // 返回新写入的数据 id
        long newQuestionId = question.getId();
        return ResultUtils.success(newQuestionId);
    }

    /**
     * 删除题目
     *
     * @param deleteRequest
     * @param request
     * @return
     */
    @PostMapping("/delete")
    @ApiOperation("删除题目")
    public BaseResponse<Boolean> deleteQuestion(@RequestBody DeleteRequest deleteRequest, HttpServletRequest request) {
        if (deleteRequest == null || deleteRequest.getId() <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        User user = userService.getLoginUser(request);
        long id = deleteRequest.getId();
        // 判断是否存在
        Question oldQuestion = questionService.getById(id);
        ThrowUtils.throwIf(oldQuestion == null, ErrorCode.NOT_FOUND_ERROR);
        // 仅本人或管理员可删除
        if (!oldQuestion.getUserId().equals(user.getId()) && !userService.isAdmin(request)) {
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
        }
        // 操作数据库
        boolean result = questionService.removeById(id);
        ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR);
        return ResultUtils.success(true);
    }

    /**
     * 更新题目（仅管理员可用）
     *
     * @param questionUpdateRequest
     * @return
     */
    @PostMapping("/update")
    @ApiOperation("更新题目（仅管理员可用）")
    @AuthCheck(mustRole = UserConstant.ADMIN_ROLE)
    public BaseResponse<Boolean> updateQuestion(@RequestBody QuestionUpdateRequest questionUpdateRequest) {
        if (questionUpdateRequest == null || questionUpdateRequest.getId() <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        //  在此处将实体类和 DTO 进行转换
        Question question = new Question();
        BeanUtils.copyProperties(questionUpdateRequest, question);
        List<QuestionContentDto> questionContent = questionUpdateRequest.getQuestionContent();
        question.setQuestionContent(JSONUtil.toJsonStr(questionContent));
        // 数据校验
        questionService.validQuestion(question, false);
        // 判断是否存在
        long id = questionUpdateRequest.getId();
        Question oldQuestion = questionService.getById(id);
        ThrowUtils.throwIf(oldQuestion == null, ErrorCode.NOT_FOUND_ERROR);
        // 操作数据库
        boolean result = questionService.updateById(question);
        ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR);
        return ResultUtils.success(true);
    }

    /**
     * 根据 id 获取题目（封装类）
     *
     * @param id
     * @return
     */
    @GetMapping("/get/vo")
    @ApiOperation("根据 id 获取题目（封装类）")
    public BaseResponse<QuestionVO> getQuestionVOById(long id, HttpServletRequest request) {
        ThrowUtils.throwIf(id <= 0, ErrorCode.PARAMS_ERROR);
        // 查询数据库
        Question question = questionService.getById(id);
        ThrowUtils.throwIf(question == null, ErrorCode.NOT_FOUND_ERROR);
        // 获取封装类
        return ResultUtils.success(questionService.getQuestionVO(question, request));
    }

    /**
     * 分页获取题目列表（仅管理员可用）
     *
     * @param questionQueryRequest
     * @return
     */
    @PostMapping("/list/page")
    @ApiOperation("分页获取题目列表（仅管理员可用）")
    @AuthCheck(mustRole = UserConstant.ADMIN_ROLE)
    public BaseResponse<Page<Question>> listQuestionByPage(@RequestBody QuestionQueryRequest questionQueryRequest) {
        long current = questionQueryRequest.getCurrent();
        long size = questionQueryRequest.getPageSize();
        // 查询数据库
        Page<Question> questionPage = questionService.page(new Page<>(current, size),
                questionService.getQueryWrapper(questionQueryRequest));
        return ResultUtils.success(questionPage);
    }

    /**
     * 分页获取题目列表（封装类）
     *
     * @param questionQueryRequest
     * @param request
     * @return
     */
    @PostMapping("/list/page/vo")
    @ApiOperation("分页获取题目列表（封装类）")
    public BaseResponse<Page<QuestionVO>> listQuestionVOByPage(@RequestBody QuestionQueryRequest questionQueryRequest,
                                                               HttpServletRequest request) {
        long current = questionQueryRequest.getCurrent();
        long size = questionQueryRequest.getPageSize();
        // 限制爬虫
        ThrowUtils.throwIf(size > 20, ErrorCode.PARAMS_ERROR);
        // 查询数据库
        Page<Question> questionPage = questionService.page(new Page<>(current, size),
                questionService.getQueryWrapper(questionQueryRequest));
        // 获取封装类
        return ResultUtils.success(questionService.getQuestionVOPage(questionPage, request));
    }

    /**
     * 分页获取当前登录用户创建的题目列表
     *
     * @param questionQueryRequest
     * @param request
     * @return
     */
    @PostMapping("/my/list/page/vo")
    @ApiOperation("分页获取当前登录用户创建的题目列表")
    public BaseResponse<Page<QuestionVO>> listMyQuestionVOByPage(@RequestBody QuestionQueryRequest questionQueryRequest,
                                                                 HttpServletRequest request) {
        ThrowUtils.throwIf(questionQueryRequest == null, ErrorCode.PARAMS_ERROR);
        // 补充查询条件，只查询当前登录用户的数据
        User loginUser = userService.getLoginUser(request);
        questionQueryRequest.setUserId(loginUser.getId());
        long current = questionQueryRequest.getCurrent();
        long size = questionQueryRequest.getPageSize();
        // 限制爬虫
        ThrowUtils.throwIf(size > 20, ErrorCode.PARAMS_ERROR);
        // 查询数据库
        Page<Question> questionPage = questionService.page(new Page<>(current, size),
                questionService.getQueryWrapper(questionQueryRequest));
        // 获取封装类
        return ResultUtils.success(questionService.getQuestionVOPage(questionPage, request));
    }

    /**
     * 编辑题目（给用户使用）
     *
     * @param questionEditRequest
     * @param request
     * @return
     */
    @PostMapping("/edit")
    @ApiOperation("编辑题目（给用户使用）")
    public BaseResponse<Boolean> editQuestion(@RequestBody QuestionEditRequest questionEditRequest, HttpServletRequest request) {
        if (questionEditRequest == null || questionEditRequest.getId() <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        // 在此处将实体类和 DTO 进行转换
        Question question = new Question();
        BeanUtils.copyProperties(questionEditRequest, question);
        List<QuestionContentDto> questionContent = questionEditRequest.getQuestionContent();
        question.setQuestionContent(JSONUtil.toJsonStr(questionContent));
        // 数据校验
        questionService.validQuestion(question, false);
        User loginUser = userService.getLoginUser(request);
        // 判断是否存在
        long id = questionEditRequest.getId();
        Question oldQuestion = questionService.getById(id);
        ThrowUtils.throwIf(oldQuestion == null, ErrorCode.NOT_FOUND_ERROR);
        // 仅本人或管理员可编辑
        if (!oldQuestion.getUserId().equals(loginUser.getId()) && !userService.isAdmin(loginUser)) {
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
        }
        // 操作数据库
        boolean result = questionService.updateById(question);
        ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR);
        return ResultUtils.success(true);
    }

    //region  AI 生成题目

    private String getGenerateQuestionUserMessage(App app, int questionNumber, int optionNumber, Set<String> avoidTitles) {
        StringBuilder userMessage = new StringBuilder();
        userMessage.append(app.getAppName()).append("\n");
        userMessage.append(app.getAppDesc()).append("\n");
        userMessage.append(AppTypeEnum.getEnumByValue(app.getAppType()).getText() + "类").append("\n");
        userMessage.append(questionNumber).append("\n");
        userMessage.append(optionNumber).append("\n");

        if (!avoidTitles.isEmpty()) {
            userMessage.append("请避免以下重复题目：").append(String.join("，", avoidTitles)).append("\n");
        }
        return userMessage.toString();
    }


    @Autowired
    private ContextMemoryService contextMemoryService;

    @PostMapping("/ai_generate")
    @ApiOperation("AI 生成题目")
    public BaseResponse<List<QuestionContentDto>> aiGenerateQuestion(@RequestBody AiGenerateQuestionRequest request) {
        Long appId = request.getAppId();
        App app = appService.getById(appId);
        ThrowUtils.throwIf(app == null, ErrorCode.NOT_FOUND_ERROR);

        Set<String> oldTitles = contextMemoryService.getRememberedTitles(appId);

        String userMessage = getGenerateQuestionUserMessage(app, request.getQuestionNumber(), request.getOptionNumber(), oldTitles);

        String result = aiManager.doSyncRequest(GENERATE_QUESTION_SYSTEM_MESSAGE, userMessage, null);
        log.info("AI 生成题目结果: {}", result);

        JSONObject resultJson = JSONUtil.parseObj(result);
        String content = extractJsonContent(resultJson.getStr("content"));
        List<QuestionContentDto> questionContentList = JSONUtil.toList(content, QuestionContentDto.class);

        // 存储新生成的题目标题
        questionContentList.forEach(q -> contextMemoryService.rememberTitle(appId, q.getTitle()));

        return ResultUtils.success(questionContentList);
    }

    private String extractJsonContent(String content) {
        if (content.startsWith("```json")) {
            content = content.substring(7);
        }
        if (content.endsWith("```")) {
            content = content.substring(0, content.length() - 3);
        }
        return content.trim();
    }



    /**
     * 使用SSE（Server-Sent Events）流式生成AI题目
     * @param aiGenerateQuestionRequest 生成题目的请求参数
     * @return SseEmitter 用于流式返回结果的SSE连接对象
     */
    @GetMapping("/ai_generate/sse")
    @ApiOperation("使用SSE流式生成AI题目")
    public SseEmitter aiGenerateQuestionSSE(AiGenerateQuestionRequest aiGenerateQuestionRequest) {
        //参数校验
        ThrowUtils.throwIf(aiGenerateQuestionRequest == null, ErrorCode.PARAMS_ERROR);
        //从请求中获取参数
        Long appId = aiGenerateQuestionRequest.getAppId();          // 应用ID
        int questionNumber = aiGenerateQuestionRequest.getQuestionNumber(); // 题目数量
        int optionNumber = aiGenerateQuestionRequest.getOptionNumber();    // 选项数量
        //根据应用ID查询应用信息
        App app = appService.getById(appId);
        ThrowUtils.throwIf(app == null, ErrorCode.NOT_FOUND_ERROR); // 应用不存在则抛出异常
        //获取该应用已记住的题目标题（用于避免重复）
        Set<String> oldTitles = contextMemoryService.getRememberedTitles(appId);
        //构造用户消息（Prompt），包含应用信息、题目数量、选项数量和已记住的标题
        String userMessage = getGenerateQuestionUserMessage(app, questionNumber, optionNumber, oldTitles);
        //创建SSE连接对象，0表示不设置超时时间
        SseEmitter emitter = new SseEmitter(0L);
        //创建列表用于存储最终生成的题目（用于后续记录标题）
        List<QuestionContentDto> questionContentList = new ArrayList<>();
        //调用AI管理器进行流式请求，获取题目生成流
        Flowable<ModelData> modelDataFlowable = aiManager.doStreamRequest(GENERATE_QUESTION_SYSTEM_MESSAGE, userMessage, null);
        //用于累积AI返回的JSON内容
        StringBuilder contentBuilder = new StringBuilder();
        //使用原子整数标记当前是否在解析JSON对象（遇到{时+1，遇到}时-1）
        AtomicInteger flag = new AtomicInteger(0);
        //处理AI返回的流式数据
        modelDataFlowable
                //指定在IO线程池执行（避免阻塞主线程）
                .observeOn(Schedulers.io())
                //从ModelData中提取AI返回的内容
                .map(chunk -> chunk.getChoices().get(0).getDelta().getContent())
                //去除所有空白字符（简化处理）
                .map(message -> message.replaceAll("\\s", ""))
                //过滤掉空消息
                .filter(StrUtil::isNotBlank)
                //将字符串拆分为字符流（便于逐个字符处理）
                .flatMap(message -> {
                    List<Character> charList = new ArrayList<>();
                    for (char c : message.toCharArray()) {
                        charList.add(c);
                    }
                    return Flowable.fromIterable(charList);
                })
                //对每个字符进行处理
                .doOnNext(c -> {
                    //遇到{表示开始一个JSON对象
                    if (c == '{') {
                        flag.incrementAndGet(); // flag加1
                    }
                    //如果当前正在解析JSON对象（flag>0）
                    if (flag.get() > 0) {
                        contentBuilder.append(c); // 累积字符到builder
                    }
                    //遇到}表示可能结束一个JSON对象
                    if (c == '}') {
                        flag.decrementAndGet(); // flag减1
                        //当flag归零，表示一个完整的JSON对象结束
                        if (flag.get() == 0) {
                            //获取累积的JSON字符串
                            String jsonStr = contentBuilder.toString();
                            //通过SSE将题目JSON发送给前端（压缩为单行）
                            emitter.send(JSONUtil.toJsonStr(jsonStr));
                            //将JSON解析为题目对象
                            QuestionContentDto questionContent = JSONUtil.toBean(jsonStr, QuestionContentDto.class);
                            //将题目添加到结果列表
                            questionContentList.add(questionContent);
                            // 清空builder，准备接收下一个JSON对象
                            contentBuilder.setLength(0);
                        }
                    }
                })
                //当流处理完成时执行
                .doOnComplete(() -> {
                    //遍历所有生成的题目，记录它们的标题
                    questionContentList.forEach(q ->
                            contextMemoryService.rememberTitle(appId, q.getTitle())
                    );
                    // 通知前端流式传输完成
                    emitter.complete();
                })
                //订阅并启动流处理
                .subscribe();
        //返回SSE连接对象
        return emitter;
    }
    //endregion
}
